/*******************************************************************************
 * Copyright 2011 @ Kapil Viren Ahuja
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.kapil.framework.logger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.SimpleLog;

import com.kapil.framework.lang.StringUtils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * Provides the very basic logging functionalities. This class provides a wrapper for
 * {@link org.apache.commons.logging.Log}.
 * 
 * The primary objective of this wrapper is to provide performance boost to the application by extending the core
 * framework for:
 * <ul>
 * <li>Provide checks for enabled logging levels before logging the messages to the sub-system.</li>
 * <li>To a <code>object.toString()</code> conversion only if the message if to be logged to the sub-system.</li>
 * </ul>
 * 
 * 
 * Logger needs a XML configuration file to be able to log to various loggers. We are using the Commons logging
 * framework for the same. This framework checks for the available logging frameworks and then initialises the logs. For
 * example: if you have the log4j's JAR file in the class path then it will initialize the LOG4J framework.
 * 
 * If no logger is provided in the class path then, by default, the commons logging framework uses the {@link SimpleLog}
 * implementation to log.
 * 
 * To obtain a reference to {@link DefaultLogger} use the {@link com.uhc.simple.common.logger.LogFactory} as:
 * 
 * <pre>
 * private final static ILogger LOGGER = LogFactory.getInstance().getLogger(DefaultLogger.class);
 * </pre>
 * 
 * This code will initialise an object with the logging sub-system pointing to the logger matching the name of the
 * class. You should always have the logger defined as final and static as there is no need to re-initialise the logger
 * once done. Re-initialising loggers again degrade performance.
 * 
 * In addition, we recommend <strong>to use the Factory to create instances.</strong> This ensures that in future if
 * there are changes in the underlying implementation, application code will not be impacted.
 * 
 */
@SuppressWarnings("rawtypes")
public class DefaultLogger implements ILogger
{  // NOPMD because loggers are defined to have these many methods.
    private static final String  token = "##";
    
    /** The logger. */
    private final Log     logger; // NOPMD because this is a logger implementation and needs initilization in constrtuctor.

    /** The XML logger to log objects as XML strings. */
    private final XStream xmlLogger;

    /** The token to replace in the messages. */

    /**
     * <p>
     * Constructor to get a logger on basis of the name of the class.
     * </p>
     * 
     * @param clazz object of {@link Class} for which the logger needs to be initialized.
     */
    public DefaultLogger(Class clazz)
    {
        this.logger = LogFactory.getLog(clazz);
        this.xmlLogger = new XStream(new DomDriver());
    }

    /**
     * <p>
     * Constructor to get a logger by its name
     * </p>
     * .
     * 
     * @param logger object of {@link String} for which the logger needs to be initialized.
     */
    public DefaultLogger(String logger)
    {
        this.logger = LogFactory.getLog(logger);
        this.xmlLogger = new XStream(new DomDriver());
    }

    /**
     * Logs messages only if the log level is set to <code>Trace</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     */
    public void trace(String message)
    {
        if (logger.isTraceEnabled())
        {
            logger.trace(message);
        }
    }

    /**
     * Logs messages replacing all tokens <code>##</code> with replacements if the log level is set to
     * <code>Trace</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacements an Array of {@link String} replacements that will replace the tokens <code>##</code> in the
     *            message
     */
    public void trace(String message, String[] replacements)
    {
        if (logger.isTraceEnabled())
        {
            this.trace(replaceTokens(message, replacements));
        }
    }

    
    /**
     * Logs messages replacing a single tokens <code>##</code> with replacements if the log level is set to
     * <code>Trace</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacement a {@link String} that will replace the tokens <code>##</code> in the message.
     */
    public void trace(String message, String replacement)
    {
        if (logger.isTraceEnabled())
        {
            this.trace(replaceToken(message, replacement));
        }
    }

    /**
     * Logs the object as XML string if the log level is set to <code>Trace</code>.
     * 
     * @param object object that needs to be logged.
     */
    public void trace(Object object)
    {
        if (logger.isTraceEnabled())
        {
            String xml = xmlLogger.toXML(object);
            logger.trace(xml);
        }
    }

    /**
     * Logs messages only if the log level is set to <code>Debug</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     */
    public void debug(String message)
    {
        if (logger.isDebugEnabled())
        {
            logger.debug(message);
        }
    }

    /**
     * Logs messages replacing all tokens <code>##</code> with replacements if the log level is set to
     * <code>Debug</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacements an Array of {@link String} replacements that will replace the tokens <code>##</code> in the
     *            message
     */
    public void debug(String message, String[] replacements)
    {
        if (logger.isDebugEnabled())
        {
            this.debug(replaceTokens(message, replacements));
        }
    }

    
    /**
     * Logs messages replacing a single tokens <code>##</code> with replacements if the log level is set to
     * <code>Debug</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacement a {@link String} that will replace the tokens <code>##</code> in the message.
     */
    public void debug(String message, String replacement)
    {
        if (logger.isDebugEnabled())
        {
            this.debug(replaceToken(message, replacement));
        }
    }


    
    /**
     * Logs the object as XML string if the log level is set to <code>Debug</code>.
     * 
     * @param object object that needs to be logged.
     */
    public void debug(Object object)
    {
        if (logger.isDebugEnabled())
        {
            String xml = xmlLogger.toXML(object);
            logger.debug(xml);
        }
    }

    /**
     * Logs messages only if the log level is set to <code>Info</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     */
    public void info(String message)
    {
        if (logger.isInfoEnabled())
        {
            logger.info(message);
        }
    }

    /**
     * Logs messages replacing all tokens <code>##</code> with replacements if the log level is set to <code>Info</code>
     * .
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacements an Array of {@link String} replacements that will replace the tokens <code>##</code> in the
     *            message
     */
    public void info(String message, String[] replacements)
    {
        if (logger.isInfoEnabled())
        {
            this.info(replaceTokens(message, replacements));
        }
    }

    
    /**
     * Logs messages replacing a single tokens <code>##</code> with replacements if the log level is set to
     * <code>Info</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacement a {@link String} that will replace the tokens <code>##</code> in the message.
     */
    public void info(String message, String replacement)
    {
        if (logger.isInfoEnabled())
        {
            this.info(replaceToken(message, replacement));
        }
    }


    /**
     * Logs the object as XML string if the log level is set to <code>Info</code>.
     * 
     * @param object object that needs to be logged.
     */
    public void info(Object object)
    {
        if (logger.isInfoEnabled())
        {
            String xml = xmlLogger.toXML(object);
            logger.info(xml);
        }
    }

    /**
     * Logs messages only if the log level is set to <code>Warn</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     */
    public void warn(String message)
    {
        if (logger.isWarnEnabled())
        {
            logger.warn(message);
        }
    }

    /**
     * Logs messages replacing all tokens <code>##</code> with replacements if the log level is set to <code>Warn</code>
     * .
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacements an Array of {@link String} replacements that will replace the tokens <code>##</code> in the
     *            message
     */
    public void warn(String message, String[] replacements)
    {
        if (logger.isWarnEnabled())
        {
            this.warn(replaceTokens(message, replacements));
        }
    }

    
    /**
     * Logs messages replacing a single tokens <code>##</code> with replacements if the log level is set to
     * <code>Warn</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacement a {@link String} that will replace the tokens <code>##</code> in the message.
     */
    public void warn(String message, String replacement)
    {
        if (logger.isWarnEnabled())
        {
            this.warn(replaceToken(message, replacement));
        }
    }


    /**
     * Logs the object as XML string if the log level is set to <code>Warn</code>.
     * 
     * @param object object that needs to be logged.
     */
    public void warn(Object object)
    {
        if (logger.isWarnEnabled())
        {
            String xml = xmlLogger.toXML(object);
            logger.warn(xml);
        }
    }

    /**
     * Logs messages only if the log level is set to <code>Error</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     */
    public void error(String message)
    {
        logger.error(message);
    }

    /**
     * Logs messages replacing all tokens <code>##</code> with replacements if the log level is set to
     * <code>Error</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacements an Array of {@link String} replacements that will replace the tokens <code>##</code> in the
     *            message
     */
    public void error(String message, String[] replacements)
    {
        this.error(replaceTokens(message, replacements));
    }

    
    /**
     * Logs messages replacing a single tokens <code>##</code> with replacements if the log level is set to
     * <code>Error</code>.
     * 
     * @param message message of type {@link String} that needs to be logged.
     * @param replacement a {@link String} that will replace the tokens <code>##</code> in the message.
     */
    public void error(String message, String replacement)
    {
        this.error(replaceToken(message, replacement));
    }


    /**
     * Logs the object as XML string if the log level is set to <code>Error</code>.
     * 
     * @param object object that needs to be logged.
     */
    public void error(Object object)
    {
        String xml = xmlLogger.toXML(object);
        logger.error(xml);
    }

    /**
     * Replace tokens in the message.
     * 
     * @param message object of {@link String} containing the message.
     * @param replacements array of {@link String} containing the replacements to replace the tokens.
     * 
     * @return the string
     */
    private String replaceTokens(String message, String[] replacements)
    {
        return StringUtils.replaceTokens(message, token, replacements, '[', ']');
    }


    /**
     * Replace a single tokens in the message.
     * 
     * @param message object of {@link String} containing the message.
     * @param replacements array of {@link String} containing the replacements to replace the tokens.
     * 
     * @return the string
     */
    private String replaceToken(String message, String replacements)
    {
        return StringUtils.replaceToken(message, token, replacements, '[', ']');
    }
}
